package cn.summit.serialization.adapter;

import cn.summit.enums.EnumVO;
import cn.summit.enums.IBaseEnum;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Enum统一序列化
 *
 * @author summit
 * @since 2020/11/13 14:03
 */
public class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {

    private final Map<String, T> nameToConstant = new HashMap<>();
    private final Map<T, EnumVO> constantToName = new HashMap<>();

    public EnumTypeAdapter(Class<T> classOfT) {
        try {
            for (T constant : classOfT.getEnumConstants()) {
                String name;
                if (constant instanceof IBaseEnum) {
                    name = ((IBaseEnum) constant).getCode();
                } else {
                    name = constant.name();
                }
                SerializedName annotation = classOfT.getField(constant.name())
                    .getAnnotation(SerializedName.class);
                if (annotation != null) {
                    name = annotation.value();
                    for (String alternate : annotation.alternate()) {
                        Assert.isNull(nameToConstant.get(alternate),
                            EnumTypeAdapter.class.getName() + "->" + classOfT.getName() + "."
                                + alternate + " alternate is duplicated");
                        nameToConstant.put(alternate, constant);
                    }
                }
                Assert.isNull(nameToConstant.get(name),
                    EnumTypeAdapter.class.getName() + " -> " + classOfT.getName() + "." + name
                        + " name is duplicated");
                nameToConstant.put(name, constant);
                EnumVO enumDTO = new EnumVO();
                if (constant instanceof IBaseEnum) {
                    enumDTO.setCode(((IBaseEnum) constant).getCode());
                    enumDTO.setName(((IBaseEnum) constant).getName());
                } else {
                    enumDTO.setCode(constant.name());
                    enumDTO.setName(constant.name());
                }
                constantToName.put(constant, enumDTO);
            }
        } catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    }

    @Override
    public T read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
        } else {
            String code = null;
            JsonToken peek = in.peek();
            if (JsonToken.STRING.equals(peek)) {
                code = in.nextString();
            } else if (JsonToken.BEGIN_OBJECT.equals(peek)) {
                in.beginObject();
                while (!JsonToken.END_OBJECT.equals(in.peek())) {
                    if ("code".equals(in.nextName())) {
                        code = in.nextString();
                    } else {
                        in.nextString();
                    }
                }
                in.endObject();
            }
            if (StringUtils.isBlank(code)) {
                return null;
            }
            return nameToConstant.get(code);
        }
    }

    @Override
    public void write(JsonWriter out, T value) throws IOException {
        if (value == null) {
            out.nullValue();
        } else {
            EnumVO enumDTO = constantToName.get(value);
            out.beginObject();
            out.name("code");
            out.value(enumDTO.getCode());
            out.name("name");
            out.value(enumDTO.getName());
            out.endObject();
        }
    }

    public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Override
        public <E> TypeAdapter<E> create(Gson gson, TypeToken<E> typeToken) {
            Class<? super E> rawType = typeToken.getRawType();
            if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
                return null;
            }
            if (!rawType.isEnum()) {
                rawType = rawType.getSuperclass();
            }
            return (TypeAdapter<E>) new EnumTypeAdapter(rawType);
        }
    };

}